Skip to content

feat(upgrade): composite upgrade action with CLI-driven JSON output#9

Open
lifanzou wants to merge 18 commits intomainfrom
lifanzou/fern-upgrade-action
Open

feat(upgrade): composite upgrade action with CLI-driven JSON output#9
lifanzou wants to merge 18 commits intomainfrom
lifanzou/fern-upgrade-action

Conversation

@lifanzou
Copy link
Copy Markdown
Contributor

@lifanzou lifanzou commented Apr 11, 2026

Summary

Converts the upgrade action from a Node.js action (dist/index.js) to a composite action that delegates the heavy lifting to the Fern CLI's new fern automations upgrade --json command (fern-api/fern#15537).

The action runs a single CLI call that internally executes both fern upgrade (CLI version) and fern generator upgrade (generator versions), returning structured JSON output. A thin JS helper (diff.js) converts this JSON into PR title, body, and commit message.

How it works

resolve-cli → verify CLI version → fern automations upgrade --json [--include-major] → format PR metadata → push + manage PR

Customer-configurable inputs

Input Default Maps to
version latest CLI version to install
fern-token (required) FERN_TOKEN env var for auth
include-major true --include-major flag on the CLI command
github-token github.token Git push + PR creation via gh CLI

Key design decisions

  • include-major defaults to true (intentional divergence from CLI default of false) — the upgrade action should aggressively upgrade, letting the preview action validate
  • Clean-slate branch model: each run resets fern/upgrade to latest main HEAD via git checkout -B
  • Single PR: all CLI + generator upgrades in one PR
  • Token masking: ::add-mask:: applied before any CLI commands
  • Fatal failures: upgrade errors fail the action with clear error messages
  • CLI version check: fails early with a clear message if the resolved CLI doesn't support fern automations upgrade

Files

  • action.yml — 6-step composite action (mask → resolve CLI → set run ID → verify CLI → run upgrade → format PR → push)
  • scripts/diff.jscliJsonToDiff() adapter + PR formatting (buildPrTitle, buildPrBody, buildCommitMessage)
  • scripts/diff.test.js — 27 unit tests (all passing)
  • README.md — Usage docs with CLI version requirements

Removed

  • scripts/snapshot.js — replaced by CLI's internal before/after tracking
  • dist/index.js, src/index.ts, package.json, tsconfig.json, tsup.config.ts — old Node.js action

Review & Testing Checklist for Human

  • Verify the CLI PR ships first: This action depends on fern automations upgrade --json from fern-api/fern#15537. The CLI version published must include that command before this action is used in production.
  • E2E test: Trigger the upgrade workflow on fern-demo/sdk-preview-test-config after the CLI PR merges and a new version publishes. Verify the generated PR has correct title, changelog table, and version bumps.
  • Verify UPGRADE_JSON env var handling: The JSON is passed via GITHUB_ENV heredoc — check that multi-line JSON doesn't break the delimiter (the UPGRADEJSONEOF terminator).
  • Check token masking: Run the action with an invalid token and verify the error message doesn't leak the token value.

Notes

  • The TODO stubs for FER-9668 (telemetry) and FER-9669 (automation config check) are kept as-is
  • The getChangelogUrl() function is kept as a fallback in diff.js for cases where the CLI doesn't provide a changelog URL (e.g., unrecognized generators). The CLI provides changelog URLs for known generators. This intentional duplication provides resilience — if the CLI changes its output format, the GHA still produces valid changelog links.
  • Previous E2E validation on the snapshot-based approach: sdk-preview-test-config#3

Link to Devin session: https://app.devin.ai/sessions/0b0ff224ce294b938ff5c2f5aec943fd


Open in Devin Review

@lifanzou lifanzou self-assigned this Apr 11, 2026
@lifanzou lifanzou changed the title feat(upgrade): implement fern-upgrade GitHub Action feat(upgrade): implement fern-upgrade GitHub Action as composite Apr 20, 2026
Barry and others added 3 commits April 25, 2026 15:27
Implements end-to-end fern-upgrade action that automates Fern CLI and
generator version upgrades. Runs `fern upgrade` and `fern generator
upgrade`, detects changes by diffing config files before/after, and
opens or updates a shared PR on the `fern/upgrade` branch.

- Add version-diff module for parsing fern.config.json and generators.yml
- Add PR management with clean-slate branch strategy (force push)
- Add PR title/body generation with changelog links from FDR
- Add github-token input with default GITHUB_TOKEN fallback
- Add CLI version resolution (auto/latest/inherit/specific)
- Stub telemetry (FER-9668) and automation config (FER-9669) as TODOs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the Node.js action (TypeScript + tsup bundle) with a composite
action to align with the repo's architectural pattern where actions are
thin shell wrappers around CLI commands.

- Rewrite action.yml from `using: node20` to `using: composite`
- Add scripts/snapshot.js for version capture (plain JS, no npm deps)
- Add scripts/diff.js for diff computation + PR content generation
- Reuse resolve-cli composite action for CLI version resolution
- Use gh CLI for PR management instead of @actions/github
- Delete all TypeScript source, tests, dist bundle, and build config
- Remove upgrade from pnpm workspace (no package.json needed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dd to fern/, fix heredoc escaping

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
@devin-ai-integration devin-ai-integration Bot force-pushed the lifanzou/fern-upgrade-action branch from e46063b to ae986f8 Compare April 25, 2026 15:30
devin-ai-integration Bot and others added 5 commits April 25, 2026 15:33
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
… PR contexts

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
… to specific versions

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
The buildwithfern.com changelog anchors include the release date
(e.g. #2026-04-24-561), which is not available at diff time. Link to
the changelog page directly instead.

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
@lifanzou lifanzou marked this pull request as ready for review April 25, 2026 15:59
@lifanzou lifanzou requested a review from Swimburger as a code owner April 25, 2026 15:59
Tests cover: computeUpgradeDiff, buildPrTitle, buildPrBody,
buildCommitMessage, getChangelogUrl, getShortGeneratorName.
Uses node:test runner to stay consistent with the composite action
pattern (no package.json, no vitest dependency).

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits April 25, 2026 16:05
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Adds ::add-mask:: for both tokens as the first step, matching the
pattern used by the preview action (core.setSecret). Prevents tokens
from leaking in CLI error output or stack traces.

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits April 25, 2026 16:25
- Use console.error instead of console.log for warnings in snapshot.js
  to prevent stdout corruption when captured via $(node ...)
- Fix include-major description: was 'When false (default)' but actual
  default is 'true'
- Update CONTRIBUTING.md: reclassify upgrade/ as composite action

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits April 25, 2026 16:47
Avoids direct ${{ inputs.include-major }} interpolation in shell,
which is a script injection anti-pattern. Passes through env: block
instead, matching the pattern used by resolve-cli, setup-cli, and
preview actions.

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Replace brittle CHANGELOG_MAP with regex derivation from the generator
name pattern (fernapi/fern-<language>-*). New generators automatically
get correct changelog links without code changes.

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Replace snapshot.js + computeUpgradeDiff with a single
`fern automations upgrade --json` call. The CLI now handles
both fern upgrade and fern generator upgrade internally and
outputs structured JSON.

Changes:
- Delete snapshot.js (CLI handles before/after internally)
- Simplify action.yml from 7 steps to 5
- Rewrite diff.js main() to read UPGRADE_JSON env var
- Add cliJsonToDiff() adapter for CLI JSON → internal format
- Remove computeUpgradeDiff() and generatorKey() (CLI does this)
- buildPrBody() now uses changelog URLs from CLI JSON
- Update all tests for new interface (27/27 pass)

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(upgrade): implement fern-upgrade GitHub Action as composite feat(upgrade): composite upgrade action with CLI-driven JSON output Apr 29, 2026
devin-ai-integration Bot and others added 2 commits April 29, 2026 05:18
…README

- Add 'Verify CLI supports automations upgrade' step that fails early
  with a clear error if the CLI is too old
- Add error checks for non-zero exit and empty JSON output from CLI
- Update README: document CLI version requirement, include-major
  divergence note, update 'How it works' for new CLI-based flow

Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Co-Authored-By: barry.zou <barry.zou@buildwithfern.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

git checkout -f -B "$BRANCH" "origin/$DEFAULT_BRANCH"

# Restore upgraded fern config on top of the clean branch
cp -r "$UPGRADE_TMP/fern/"* fern/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Shell glob * silently skips dotfiles when restoring upgraded fern config

On line 144, cp -r "$UPGRADE_TMP/fern/"* fern/ uses a shell glob that does not match dotfiles (files starting with .) by default in bash. The preceding cp -r fern/ "$UPGRADE_TMP/fern" on line 137 copies ALL files (including dotfiles) from the upgraded state. After switching to the clean branch, only non-dotfile entries are restored. If fern automations upgrade ever creates or modifies a dotfile at the top level of the fern/ directory (e.g., .fernignore), those changes would be silently lost in the PR.

Fix options

Either enable dotglob before the copy:

shopt -s dotglob
cp -r "$UPGRADE_TMP/fern/"* fern/
shopt -u dotglob

Or use rsync/find-based copy that includes hidden files:

cp -a "$UPGRADE_TMP/fern/." fern/
Suggested change
cp -r "$UPGRADE_TMP/fern/"* fern/
cp -a "$UPGRADE_TMP/fern/." fern/
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant